package com.github.cosycode.imgtool.ctrl;

import com.github.cosycode.common.ext.bean.DoubleBean;
import com.github.cosycode.common.lang.BaseRuntimeException;
import com.github.cosycode.common.util.otr.PrintTool;
import com.github.cosycode.imgtool.rule.CheckConfig;
import com.github.cosycode.imgtool.rule.MarkdownRepairSource;
import com.github.cosycode.imgtool.util.LogUtils;
import com.github.cosycode.imgtool.util.PathUtils;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * <b>Description : </b>
 * <p>
 * <b>created in </b> 2022/8/7
 * </p>
 *
 * @author pengfchen
 * @since 1.0
 **/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MarkdownImgLinkMatchAnalyzer {

    /**
     * 通过修改文件内容 link 来修理图片链接
     *
     * @param markdownFileSet       markdown 文件路径集合(文件的绝对路径)
     * @param picFileSet            图片文件路径集合(图片的绝对路径, 且图片文件的名称不能重复)
     * @param config                check 配置
     * @param mdImgLinkModifyHandle 用于替换 markdown 内容的函数式接口
     * @param dryRun                是否 dryRun
     * @return 文件图片链接的匹配情况
     * @throws IOException 文件读取异常
     */
    @SuppressWarnings("java:S117")
    public static Map<String, List<DoubleBean<String, String>>> execMdImgLinkMatch(Set<String> markdownFileSet, Set<String> picFileSet,
                                                                                   CheckConfig config, IMdImgLinkModifyHandle mdImgLinkModifyHandle, final boolean dryRun) throws IOException {
        LogUtils.logEvent(() -> MarkdownRepairSource.analyzeRepeatFileName(markdownFileSet).printLog(), "文件重复名检查");

        MarkdownRepairSource.RepeatFileNameReport imgNameRepeatReport = MarkdownRepairSource.analyzeRepeatFileName(picFileSet);
        LogUtils.logEvent(imgNameRepeatReport::printLog, "图片重复名检查");
        if (!imgNameRepeatReport.getRepeatNameListMap().isEmpty()) {
            imgNameRepeatReport.clearRepeatFile(true);
            throw new BaseRuntimeException("在搜索文件之前, 需要保证图片文件名唯一 : {}", imgNameRepeatReport.getRepeatNameListMap());
        }
        Map<String, String> name$path$imgMap = imgNameRepeatReport.getUniqueNameMap();

        // 用于存放 md 文件内失效 img 链接, 但是找到匹配的图片的<匹配到的图片路径, 文件内链接指向路径>
        Map<String, List<DoubleBean<String, String>>> listMap = new HashMap<>();

        // 3. 遍历文件
        for (String mdFilePath : markdownFileSet) {
            File file = new File(mdFilePath);
            config.setRelatePathEnable(file.getCanonicalPath());
            MarkdownFileProcessor.replaceMdImgLink(file, config, rst -> {
                boolean exist = rst.isExist();
                if (exist) {
                    String newImgLinkText = mdImgLinkModifyHandle.compute(file, PathUtils.concatPathClips(rst.getRealImgPath()));
                    if (StringUtils.isNotBlank(newImgLinkText) && !rst.getOriginalPath().equals(newImgLinkText)) {
                        List<DoubleBean<String, String>> doubleBeans = listMap.computeIfAbsent(mdFilePath, k -> new ArrayList<>());
                        doubleBeans.add(DoubleBean.of(rst.getRealImgPath(), newImgLinkText));
                        return newImgLinkText;
                    }
                    return null;
                } else {
                    // 若不存在, 则到 name$path$Map 里面匹配.
                    String matchPath = name$path$imgMap.get(rst.getImgFileName());
                    if (matchPath == null) {
                        return null;
                    }
                    String newImgLinkText = mdImgLinkModifyHandle.compute(file, PathUtils.concatPathClips(matchPath));
                    if (StringUtils.isNotBlank(newImgLinkText) && !rst.getOriginalPath().equals(newImgLinkText)) {
                        List<DoubleBean<String, String>> doubleBeans = listMap.computeIfAbsent(mdFilePath, k -> new ArrayList<>());
                        doubleBeans.add(DoubleBean.of(matchPath, newImgLinkText));
                        return newImgLinkText;
                    }
                    return null;
                }
            }, dryRun);
        }
        return listMap;
    }

    /**
     * 生成 markdown 文件内图片链接匹配的情况信息, 返回值如下
     * <p>
     * <br/> null: 表示因图片文件名称重复等相关原因无法进行匹配, 需要等待人工处理.
     * <br/> DoubleBean<Map<String, String>, List<DoubleBean<String, String>>>
     * o1: 匹配成功后移动图片的 from -> to 路径
     * o2: 匹配不成功后的失效 img 链接 (文件名, 图片链接名)
     * </p>
     *
     * @param markdownFileSet markdown 文件路径集合(文件的绝对路径)
     * @param picFileSet      图片文件路径集合(图片的绝对路径, 且图片文件的名称不能重复)
     * @return 文件图片链接的匹配情况
     * @throws IOException 文件读取异常
     */
    @SuppressWarnings("java:S117")
    public static MarkdownImgLinkMatchReport computeMdImgLinkMatchesInfo(Set<String> markdownFileSet, Set<String> picFileSet, CheckConfig config) throws IOException {
        LogUtils.logEvent(() -> MarkdownRepairSource.analyzeRepeatFileName(markdownFileSet).printLog(), "文件重复名检查");

        MarkdownRepairSource.RepeatFileNameReport imgNameRepeatReport = MarkdownRepairSource.analyzeRepeatFileName(picFileSet);
        LogUtils.logEvent(imgNameRepeatReport::printLog, "图片重复名检查");
        if (!imgNameRepeatReport.getRepeatNameListMap().isEmpty()) {
            imgNameRepeatReport.clearRepeatFile(true);
            throw new BaseRuntimeException("在搜索文件之前, 需要保证图片文件名唯一 : {}", imgNameRepeatReport.getRepeatNameListMap());
        }
        Map<String, String> name$path$imgMap = imgNameRepeatReport.getUniqueNameMap();

        // active links
        List<String> activeLinkList = new ArrayList<>();
        // 用于存放 md 文件内失效 img 链接, 但是找到匹配的图片的<匹配到的图片路径, 文件内链接指向路径>
        Map<String, String> fromPath$toPath$Map = new HashMap<>();
        // 用于存放 md 文件内失效 img 链接, 且通过文件名没有找到对应图片的链接
        List<DoubleBean<String, String>> mdFile$picPath$douList = new ArrayList<>();

        // 3. 遍历文件
        for (String mdFilePath : markdownFileSet) {
            File file = new File(mdFilePath);
            MarkdownFileProcessor.findMdImgLink(file, config, rst -> {
                boolean exist = rst.isExist();
                if (exist) {
                    activeLinkList.add(rst.getRealImgPath());
                    return;
                }
                // 若不存在, 则到 name$path$Map 里面匹配.
                String matchPath = name$path$imgMap.get(rst.getImgFileName());
                if (matchPath == null) {
                    // 未匹配到的 picture
                    mdFile$picPath$douList.add(DoubleBean.of(mdFilePath, rst.getRealImgPath()));
                } else {
                    fromPath$toPath$Map.put(matchPath, rst.getRealImgPath());
                }
            });
        }
        MarkdownImgLinkMatchReport report = new MarkdownImgLinkMatchReport();
        report.setMatchedLink(fromPath$toPath$Map);
        report.setNonMatchesLinkList(mdFile$picPath$douList);
        report.setActiveLinkList(activeLinkList);
        return report;
    }

    @FunctionalInterface
    public interface IMdImgLinkModifyHandle {
        /**
         * 生成函数式接口:
         * 将 markdown link 改成 <p> /{filename}/{imageName} </p> 的格式
         *
         * @param rootPath 根路径
         * @return 生成的函数式接口
         */
        static IMdImgLinkModifyHandle geneRootPathHandle(String rootPath) {
            final String rootPath0 = PathUtils.concatPathClips(rootPath);
            return (mdFile, imgAbsolutePath) -> {
                assert imgAbsolutePath.contains(rootPath0);
                return "/" + mdFile.getName() + "/" + PathUtils.getFileName(imgAbsolutePath);
            };
        }

        /**
         * 生成函数式接口:
         * 将 markdown link 改成 <p> ./{relativePathPrefix}/{imageName} </p> 的格式
         *
         * @param relativePathPrefix 相对路径前缀
         * @return 生成的函数式接口
         */
        static IMdImgLinkModifyHandle geneRelativePathHandle(String relativePathPrefix) {
            return (mdFile, imgAbsolutePath) -> String.format("./%s/%s/%s", relativePathPrefix,
                    PathUtils.getFileNamePrefix(mdFile.getName()), PathUtils.getFileName(imgAbsolutePath));
        }

        /**
         * 生成函数式接口:
         * 如果 找到的图片的 绝对路径前缀是 matchAbsolutePathPrefix, 那么将前缀替换成 linkPrefix
         *
         * @param matchAbsolutePathPrefix 图片的 绝对路径前缀
         * @param linkPrefix              替换的 link 前缀
         * @return 生成的函数式接口
         */
        static IMdImgLinkModifyHandle geneReplacePrefixHandle(String matchAbsolutePathPrefix, String linkPrefix) {
            return (mdFile, imgAbsolutePath) -> {
                if (imgAbsolutePath.startsWith(matchAbsolutePathPrefix)) {
                    return linkPrefix + imgAbsolutePath.substring(matchAbsolutePathPrefix.length());
                }
                return null;
            };
        }

        /**
         * @param mdFile          markdown 文件名称
         * @param imgAbsolutePath 图片绝对路径
         * @return 最终替换的路径, 若为 null 则表示不替换
         */
        String compute(File mdFile, String imgAbsolutePath);

    }

    @Data
    @SuppressWarnings("java:S116")
    public static class MarkdownImgLinkMatchReport {
        /**
         * the link list that exist.
         */
        List<String> activeLinkList;
        /**
         * 用于存放 md 文件内失效 img 链接, 但是找到匹配的图片的<匹配到的图片路径, 文件内链接指向路径>
         */
        Map<String, String> matchedLink;
        /**
         * 用于存放 md 文件内失效 img 链接, 且通过文件名没有找到对应图片的链接
         */
        List<DoubleBean<String, String>> nonMatchesLinkList;

        public void printLog() {
            PrintTool.info("active link: {}, matched link: {}, not matched link: {}",
                    activeLinkList.size(), matchedLink.size(), nonMatchesLinkList.size());
        }

        public void printMatchLog() {
            Objects.requireNonNull(matchedLink);
            // 打印匹配到 picture
            matchedLink.forEach((k, v) -> PrintTool.success("[匹配] from:{} ==> to:{}", k, v));
        }

        public void printNonMatchLog() {
            Objects.requireNonNull(nonMatchesLinkList);
            // 打印 未匹配到 picture
            for (DoubleBean<String, String> bean : nonMatchesLinkList) {
                PrintTool.warn("[未匹配] {} ==> {}", bean.getO1(), bean.getO2());
            }
        }

        public void printMatchLog(String ignore) {
            Objects.requireNonNull(matchedLink);
            // 打印匹配到 picture
            matchedLink.forEach((k, v) -> PrintTool.success("[匹配] from:{} ==> to:{}",
                    StringUtils.removeStart(k, ignore), StringUtils.removeStart(v, ignore)));
        }

        public void printNonMatchLog(String ignore) {
            Objects.requireNonNull(nonMatchesLinkList);
            // 打印 未匹配到 picture
            for (DoubleBean<String, String> bean : nonMatchesLinkList) {
                PrintTool.warn("[未匹配] {} ==> {}",
                        StringUtils.removeStart(bean.getO1(), ignore), StringUtils.removeStart(bean.getO2(), ignore));
            }
        }
    }

}
